// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: date.cpp 225 2008-09-12 17:43:02Z heavyzheng $
//

#ifndef ZZHENG_QUANT_DATES_SOURCE
#define ZZHENG_QUANT_DATES_SOURCE
#endif // ZZHENG_QUANT_DATES_SOURCE

#include <zzheng/quant/dates/date.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(push)
#  pragma warning(disable: 4127 4244 4511 4512 4701)
#endif // ZZHENG_MSVC

#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/lexical_cast.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(pop)
#endif // ZZHENG_MSVC

#include <exception>
#include <string>

namespace zzheng {
namespace quant {
namespace dates {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // constructors
    //

    date::date(): date_(boost::gregorian::not_a_date_time) {
        // Do nothing.
    }

    date::date(year_type year, month_type month, day_type day): date_() {
        try {
            date_ = boost::gregorian::date(year, month, day);
        } catch (const std::out_of_range&) {
            std::string errmsg = "(year="  + boost::lexical_cast<std::string>(year)
                               + ",month=" + boost::lexical_cast<std::string>(month)
                               + ",day="   + boost::lexical_cast<std::string>(day)
                               + ") out of range.";
            throw std::out_of_range(errmsg.c_str());
        }
    }

    date::date(const boost::gregorian::date& rhs): date_(rhs) {
        // Do nothing.
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // comparison operators
    //

    bool date::operator==(const date& rhs) const {
        return (date_ == rhs.date_);
    }

    bool date::operator<(const date& rhs) const {
        return (date_ < rhs.date_);
    }

    bool date::operator!=(const date& rhs) const {
        return !operator==(rhs);
    }

    bool date::operator>(const date& rhs) const {
        return (!operator==(rhs) && !operator<(rhs));
    }

    bool date::operator<=(const date& rhs) const {
        return (operator==(rhs) || operator<(rhs));
    }

    bool date::operator>=(const date& rhs) const {
        return !operator<(rhs);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // increment/decrement operators
    //

    date& date::operator++() {
        add_days(1);
        return *this;
    }

    date date::operator++(int) {
        date tmp = *this;
        add_days(1);
        return tmp;
    }

    date& date::operator--() {
        add_days(-1);
        return *this;
    }

    date date::operator--(int) {
        date tmp = *this;
        add_days(-1);
        return tmp;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // plus/minus operators
    //

    date date::operator+(int delta) const {
        date tmp = *this;
        tmp.add_days(delta);
        return tmp;
    }

    date date::operator-(int delta) const {
        return operator+(-delta);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // member functions
    //

    void date::swap(date& rhs) {
        date tmp(*this);
        *this = rhs;
        rhs = tmp;
    }

    bool date::empty() const {
        return (date_.is_not_a_date());
    }

    int date::julian_day() const {
        return date_.julian_day();
    }

    date::year_type date::year() const {
        return date_.year();
    }

    date::month_type date::month() const {
        return date_.month();
    }

    date::day_type date::day() const {
        return date_.day();
    }

    date::weekday_type date::weekday() const {
        switch (date_.day_of_week().as_enum()) {
        case boost::gregorian::Monday:
            return monday;
        case boost::gregorian::Tuesday:
            return tuesday;
        case boost::gregorian::Wednesday:
            return wednesday;
        case boost::gregorian::Thursday:
            return thursday;
        case boost::gregorian::Friday:
            return friday;
        case boost::gregorian::Saturday:
            return saturday;
        case boost::gregorian::Sunday:
            return sunday;
        default:
            assert(! "Illegal day of week enum (boost::gregorian::day_of_week).");
            return monday;
        }
    }

    bool date::is_end_of_month() const {
        // TODO: implement a more efficient version?
        return (end_of_month() == *this);
    }

    date date::end_of_month() const {
        date eom;
        eom.date_ = date_.end_of_month();
        return eom;
    }

    date& date::add_days(int delta) {
        date_ += boost::gregorian::days(delta);
        return *this;
    }

    date& date::add_weeks(int delta) {
        date_ += boost::gregorian::weeks(delta);
        return *this;
    }

    date& date::add_months(int delta, bool end_of_month) {
        // Note: boost::gregorian durations always use end-of-month.
        day_type original_day = day();
        date_ += boost::gregorian::months(delta);
        if (!end_of_month && original_day < day()) {
            date_ = boost::gregorian::date(year(), month(), original_day);
        }
        return *this;
    }

    date& date::add_years(int delta, bool end_of_month) {
        // Note: boost::gregorian durations always use end-of-month.
        day_type original_day = day();
        date_ += boost::gregorian::years(delta);
        if (!end_of_month && original_day < day()) {
            date_ = boost::gregorian::date(year(), month(), original_day);
        }
        return *this;
    }

    std::string date::str() const {
        return boost::gregorian::to_iso_extended_string(date_);
    }

} // namespace zzheng::quant::dates
} // namespace zzheng::quant
} // namespace zzheng



